﻿#region License
// /* **************************************************************
// * Copyright (C) intex Consulting GmbH - All Rights Reserved    *
// *--------------------------------------------------------------*
// * Unauthorized copying of this file, via any medium is         *
// * strictly prohibited                                          *
// *--------------------------------------------------------------*
// * Proprietary and confidential                                 *
// *--------------------------------------------------------------*
// * Dissasembling/modifications strictly prohibited!             *
// ***************************************************************/
#endregion
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Sockets;
using System.Security;
using System.Text;
using System.Threading;
using ComHlp.Classes.Extensions;
using ComHlp.Classes.Parser;
using ComHlp.Classes.Parser.Supplementary;
using ComHlp.ContentClasses;
using ComHlp.ContentClasses.Connections;
using ComHlp.ContentClasses.VirtualControls;
using ComHlp.ContentClasses.VirtualControls.Buttons;
using ComHlp.ContentClasses.VirtualControls.LView;
using ComHlp.ContentClasses.VirtualControls.ProgramExplorer;
using ComHlp.enums;
using ComHlp.Exceptions;

namespace ComHlp.Classes.Net
{
    public class ItxCom
    {
        /// <summary> used temporary files for cleanup at close </summary>
        private readonly List<string> _temporaryFiles = new List<string>();

        /// <summary> this is the first user context client instance? </summary>
        private readonly bool _firstInstance;

        /// <summary> internal clientversion </summary>
        private readonly string _clientVersion;

        private readonly bool _requestProgramExplorer;

        /// <summary> connections network stream </summary>
        private NetworkStream _netStream;

        /// <summary> connections tcp client </summary>
        private TcpClient _tcpClient;

        /// <summary> received server header, user for communication </summary>
        private string _serverHeader = "";

        /// <summary> timer for sending keepalives </summary>
        private Timer _keepAliveTimer;

        /// <summary> the default keepalive period </summary>
        private const int KeepAlivePeriod = 15000;

        /// <summary> network timeout for streams! </summary>
        private readonly int _networkTimeout;

        /// <summary> thread synch mutex </summary>
        private Mutex _comInProcess = new Mutex();

        /// <summary> variable which is polled until network data are available in getresponse method </summary>
        private bool _continueReceive = true;

        /// <summary> codepage converter class </summary>
        private readonly CodepageConverter _codepageConverter;

        /// <summary> current connection </summary>
        private ClientConnection Connection { get; }

        /// <summary> necessary for compression - send buffer to compare with last </summary>
        private byte[] _sendBufferBase;

        /// <summary> necessary for compression - send buffer to compare with previous </summary>
        private byte[] _recvBufferBase = new byte[9439];

        /// <summary> program explorer / server macros already requested? </summary>
        private bool _logonDataRequested;

        private bool _versionLicenseRequested;


        /// <summary> Initialize threaded srv com </summary>
        /// <param name="connection"></param>
        /// <param name="firstInstance"></param>
        /// <param name="terminalNumber"></param>
        /// <param name="clientVersion"></param>
        /// <param name="networkTimeout"></param>
        /// <param name="requestProgramExplorer"></param>
        /// <param name="safeUtf8"></param>
        /// <exception cref="ArgumentNullException"></exception>
        public ItxCom(ClientConnection connection, bool firstInstance, ushort terminalNumber, string clientVersion, int networkTimeout, bool requestProgramExplorer, bool safeUtf8)
        {
            if (connection == null) throw new ArgumentNullException(nameof(connection));

            // init communications
            Connection = connection;
            ComStatics.SetConnectionData(connection.Codepage, terminalNumber, safeUtf8);
            _codepageConverter = new CodepageConverter(connection.Codepage);

            _firstInstance = firstInstance;
            _clientVersion = clientVersion;
            _requestProgramExplorer = requestProgramExplorer;
            _networkTimeout = networkTimeout < 1000 ? 120000 : networkTimeout;

            // intex compression? init buffer
            if (connection.Compression != ItxCompression.ClientV5)
                return;

            _sendBufferBase = new byte[9439];
            for (int i = 0; i < 9439; i++)
                _sendBufferBase[i] = 0x20;
        }

        ~ItxCom()
        {
            // temp files..
            foreach (var tempFile in _temporaryFiles)
            {
                try
                {
                    File.Delete(tempFile);
                }
                catch (IOException) {}
                catch (SecurityException) {}
                catch (UnauthorizedAccessException) {}
                catch (NotSupportedException) {}
            }

            // be sure to release all resources
            Disconnect();
        }

        // <summary> Check if the current socket connection is still connected </summary>
        // <returns>true/false</returns>
        public bool IsConnected()
        {
            return _tcpClient != null && _tcpClient.Client.Connected;
        }

        /// <summary> connect to intex server </summary>
        /// <exception cref="CommunicationException">.</exception>
        /// <exception cref="EncodingException">Condition.</exception>
        public ItxServerMessage ConnectToServer()
        {
            return PerformOperation(ItxComOperation.Connect, ItxServerMessageType.Ok, "");
        }

        /// <summary> disconnect from intex server </summary>
        /// <exception cref="CommunicationException">Condition.</exception>
        /// <exception cref="EncodingException">Condition.</exception>
        public void DisconnectFromServer()
        {
            _continueReceive = false;
            PerformOperation(ItxComOperation.Disconnect, ItxServerMessageType.Ok, "");
        }

        /// <summary> send a general request to server </summary>
        /// <param name="msgType"></param>
        /// <param name="message"></param>
        /// <returns></returns>
        /// <exception cref="EncodingException">Condition.</exception>
        /// <exception cref="CommunicationException">Condition.</exception>
        public ItxServerMessage SendRequestToServer(ItxServerMessageType msgType, string message)
        {
            return PerformOperation(ItxComOperation.ClientServer, msgType, message);
        }

        /// <summary> perform passed operation </summary>
        /// <param name="operation"></param>
        /// <param name="msgType"></param>
        /// <param name="message"></param>
        /// <returns></returns>
        /// <exception cref="CommunicationException"></exception>
        /// <exception cref="EncodingException">Condition.</exception>
        private ItxServerMessage PerformOperation(ItxComOperation operation, ItxServerMessageType msgType, string message)
        {
            // pause keepalivetimer
            _keepAliveTimer?.Change(Timeout.Infinite, Timeout.Infinite); // == null if no connection was made

            // wait if communication (keepalive) is in progress
            try
            {
                _comInProcess?.WaitOne();
            }
            catch (ObjectDisposedException)
            {
                _comInProcess = new Mutex();
            }
            catch (AbandonedMutexException)
            {
                _comInProcess = new Mutex();
            }

            ItxServerMessage serverMessage;
            try
            {
                // determine what to do
                switch (operation)
                {
                    case ItxComOperation.Disconnect:
                        Disconnect();
                        return null;

                    case ItxComOperation.Connect:
                        Connect();
                        serverMessage = GetDefaultResponse(false);
                        break;

                    case ItxComOperation.ClientServer:
                        SendMessage(msgType, message);
                        serverMessage = GetDefaultResponse(false);
                        break;

                    default:
                        throw new CommunicationException("Invalid operation!");
                }
            }
            catch (CommunicationException)
            {
                // cleanup and return
                Disconnect();
                _keepAliveTimer?.Dispose();
                throw;
            }
            catch (EncodingException)
            {
                // cleanup and return
                Disconnect();
                _keepAliveTimer?.Dispose();
                throw;
            }
            finally
            {
                // ReSharper disable once ExceptionNotDocumented
                _comInProcess?.ReleaseMutex();
            }

            // restart timer
            _keepAliveTimer?.Change(KeepAlivePeriod, KeepAlivePeriod);

            return serverMessage;
        }


        /// <summary> Connect to connection passed to the constructor </summary>
        /// <exception cref="CommunicationException"></exception>
        private void Connect()
        {
            // connect to server
            try
            {
                _tcpClient = new TcpClient(Connection.ServerHostname, Connection.ServerService);
            }
            catch (SocketException sExc)
            {
                throw new CommunicationException(sExc, "Connection failed: {0}", sExc.ErrorCode);
            }

            if (!_tcpClient.Client.Connected)
                throw new CommunicationException("Unable to connect - unknown reason");

            // set buffer size
            _tcpClient.SendBufferSize = 8192;

            // get stream
            try
            {
                _netStream = _tcpClient.GetStream();
            }
            catch (ObjectDisposedException exc)
            {
                throw new CommunicationException(exc, "Connection closed.");
            }

            if (_netStream.CanRead && _netStream.CanWrite)
            {
                _netStream.ReadTimeout = _networkTimeout;
                _netStream.WriteTimeout = _networkTimeout;

                DoHandshake();
                // we do not init start the timer here - subsequent processing classes will request more information at connect
                // after the information request is done it will get automatically started
                _keepAliveTimer = new Timer(SendKeepAliveTimerCallBack, null, Timeout.Infinite, KeepAlivePeriod);
            }
            else
                throw new CommunicationException("Connection opened, but can't handle.");
        }

        /// <summary> Do the intial handshake </summary>
        private void DoHandshake()
        {
            //
            // send initial message
            string conStr = Connection.I5Host ? $"{Connection.I5Pgm.PadRight(12)}AS4PRD{ComStatics.TerminalNumberFormatted}" : $"itcssa20 PRD{ComStatics.TerminalNumberFormatted}.";
            SendData(Connection.Codepage, conStr, ItxServerMessageType.Unknown);

            //
            // get response and extract serverheader which we have to store for further requests
            ItxServerMessage serverMessage = GetDefaultResponse(true);
            if (serverMessage.Response.Length == 0)
                throw new CommunicationException("Invalid server response received!");
            _serverHeader = serverMessage.Response;

            //
            // build protocoll informational message
            StringBuilder sbMessage = new StringBuilder(_serverHeader);
            string ipAddr = ComHelper.GetIpAddress(_tcpClient.Client.LocalEndPoint);
            sbMessage.Replace("SERV", "KON ");

            /* add initial header informations for connection
             * MeldTYP = KON
             * Restpaket
             * Filler		    X(30)	leer
             * Logterm		    X(08)	TERMnnn mit nnn=Terminalnummer
             * Kennz		    X(01)	W = Windows
             * Filler		    X(03)	(leer/MDE/WEB)
             * Instanz		    9(02)   TODO
             * Version		    X(04)	z.B. „502f“
             * Dicke Buttons	X01)	J=ja
             * Reconnect-port	9(05)
             * Interface	    X(01)	J=Client hat Interface angeschlossen
             * IP		        X(18)	IP des Clients in der Form aaabbbcccddd
             * Soll-Mandant	X(02) 	Vorgabe
             * Soll-Sprache	X(02)	Vorgabe
             * Proto-Version	X(04)	aktuell 0015 (wichtig für neue Funktionen!)
            */
            string instance = _firstInstance ? "01" : "02";
            sbMessage.Append($"                              TERM{ComStatics.TerminalNumberFormatted} WHGH{instance}{_clientVersion} 00000 {ipAddr}");

            // set values before KON to 000000000 (just for fun ... nah don't really know what to do...)
            sbMessage.Replace(sbMessage.ToString(12, 9), _serverHeader.Length.ToString("000000000"));

            // add mandantory/client
            sbMessage.Append("  ");

            // add language (sprkz)
            sbMessage.Append("  ");

            // add protocol version
            sbMessage.Append("0015");

            // add blanks up to char 470
            string message = sbMessage.ToString();
            message = message.PadRight(470);

            // append startprog
            if (Connection.StartProgram.Length > 0 && Connection.StartProgram.Length < 8)
            {
                string startProg = Connection.StartProgram.PadRight(8);
                message = message + startProg;
            }

            // send 
            SendData(Connection.Codepage, message, ItxServerMessageType.Unknown);
        }

        /// <summary> Disconnects the connection </summary>
        private void Disconnect()
        {
            _continueReceive = false;

            // close stream and client with cleanup
            if (_netStream != null)
            {
                if (_netStream.CanRead || _netStream.CanWrite)
                    _netStream.Close();
                _netStream.Dispose();
                _netStream = null;
            }

            if (_tcpClient == null)
                return;

            if (_tcpClient.Connected)
            {
                _tcpClient.Client.Shutdown(SocketShutdown.Both);
                _tcpClient.Close();
            }

            _tcpClient = null;
        }

        /// <summary> Build and send message </summary>
        /// <param name="msgType"></param>
        /// <param name="message"></param>
        /// <exception cref="CommunicationException"></exception>
        private void SendMessage(ItxServerMessageType msgType, string message)
        {
            // default init with serverheader
            StringBuilder sbMessage = new StringBuilder(_serverHeader);
            switch (msgType)
            {
                case ItxServerMessageType.FormDef:
                    sbMessage.Replace("SERV", "FRM ");
                    // add map
                    sbMessage.Append(message);
                    // finalize server header with 000000000
                    sbMessage.Replace(sbMessage.ToString(12, 9), message.Length.ToString("000000000"));
                    break;

                case ItxServerMessageType.ScreenContent:
                    sbMessage.Replace("SERV", Connection.Compression == ItxCompression.None ? "BS  " : "BS01");
                    // add map
                    sbMessage.Append(message);
                    break;

                case ItxServerMessageType.ListView:
                    sbMessage.Replace("SERV", "LV  ");
                    break;

                case ItxServerMessageType.Graphics:
                    sbMessage.Replace("SERV", "GRA ");
                    break;

                case ItxServerMessageType.LicenseData:
                    sbMessage.Replace("SERV", "Z001");
                    break;

                case ItxServerMessageType.VersionData:
                    sbMessage.Replace("SERV", "Z002");
                    break;

                case ItxServerMessageType.ProgramExplorer:
                    sbMessage.Replace("SERV", "MI2 ");
                    if (!string.IsNullOrEmpty(message))
                        sbMessage.Append(message);
                    break;

                case ItxServerMessageType.Favorites:
                    sbMessage.Replace("SERV", "FI1 ");
                    sbMessage.Append(!string.IsNullOrEmpty(message) ? message : "".PadRight(122));
                    break;

                case ItxServerMessageType.Pause:
                    sbMessage.Replace("SERV", "PAUS");
                    break;

                case ItxServerMessageType.FileGet:
                    sbMessage.Replace("SERV", "FG  ");
                    sbMessage.Append(message);
                    break;

                case ItxServerMessageType.FileGetBinary:
                    sbMessage.Replace("SERV", "FGB ");
                    sbMessage.Append(message);
                    break;

                case ItxServerMessageType.Ok:
                    sbMessage.Replace("SERV", "OK  ");
                    break;
            }

            /* it may happen that the disconnect took to long when form close is clicked
               and another keepalive/pause is sent already/dispatched to queue */
            if (_netStream == null)
                return;

            SendData(Connection.Codepage, sbMessage.ToString(), msgType);
        }

        /// <summary> send pause/keepalive every defined timer delay </summary>
        /// <param name="state"></param>
        private void SendKeepAliveTimerCallBack(object state)
        {
            if (!IsConnected())
                return;

            _comInProcess.WaitOne();

            try
            {
                SendMessage(ItxServerMessageType.Pause, null);
                GetDefaultResponse(true);
            }
            catch (CommunicationException)
            {
                // do nothing?
            }
            finally
            {
                _comInProcess.ReleaseMutex();
            }
        }


        /// <summary> method for requesting a completly parsed mapped inclusive cache handling </summary>
        /// <param name="currentBs"></param>
        /// <returns></returns>
        /// <exception cref="CommunicationException">Errenous mapname '{0}' received!</exception>
        public MapParsingResult RequestMapData(string currentBs)
        {
            // return class
            MapParsingResult mapParsingResult = new MapParsingResult();

            // current bs sentence as string builder 
            StringBuilder sbCurrentBs = new StringBuilder(currentBs);
            BsParser bsParser = new BsParser();

            // get mapname
            string map = bsParser.GetMapNameFromBs(sbCurrentBs);
            if (map.Length <= 0)
                throw new CommunicationException($"Errenous mapname '{map}' received!");

            // when we reach GM01 we need to clear formdef and listview cache
            if (map == "GM01")
            {
                FormDefCache.ClearMapCache();
                _logonDataRequested = false;
            }

            // got map cached?
            ItxServerMessage serverMessage;
            if (!FormDefCache.CheckMapCache(map))
            {
                // if not, we request it
                try
                {
                    serverMessage = PerformOperation(ItxComOperation.ClientServer, ItxServerMessageType.FormDef, map);
                }
                catch (EncodingException exc)
                {
                    throw new CommunicationException(exc, exc.Message);
                }

                // add requested data to cache
                FormDefParser formDefParser = new FormDefParser();
                formDefParser.AddMapToCache(serverMessage.Response);
            }

            // get the map from the cache
            mapParsingResult.FormDefMap = FormDefCache.GetCachedMap(map);
            if (mapParsingResult.FormDefMap == null)
                throw new CommunicationException($"Unable to request/parse map '{map}'!");

            // if initialconnect we need to handle license+verison information
            if (!_versionLicenseRequested)
            {
                _versionLicenseRequested = true;
                RequestVersionAndLicense(mapParsingResult);
            }

            /* program explorer - if we are connected
             * another ugly foo... we have no idea, when we are logged in
             * so we assume that we are logged in, if we have received another map from server beside gm01
             */
            if (!_logonDataRequested && mapParsingResult.FormDefMap.Header.Name != "GM01")
            {
                _logonDataRequested = true;
                RequestAndParseFavorites(mapParsingResult);
                if(_requestProgramExplorer)
                    RequestAndParseProgramExplorer(mapParsingResult);
            }

            // parse bs before adding formdef!
            mapParsingResult.BsMap.Header = bsParser.ParseBsHead(sbCurrentBs);
            mapParsingResult.BsMap.WindowTitle = $"{mapParsingResult.FormDefMap.Header.Name} - {mapParsingResult.FormDefMap.Header.Title}";

            //
            // the listview
            if (mapParsingResult.BsMap.Header.ListView && mapParsingResult.FormDefMap.Header.InternalListViewCount > 0)
            {
                // get listview layout
                try
                {
                    serverMessage = PerformOperation(ItxComOperation.ClientServer, ItxServerMessageType.ListView, "");
                }
                catch (EncodingException exc)
                {
                    throw new CommunicationException(exc, exc.Message);
                }

                // parse
                ListViewParser listViewParser = new ListViewParser();
                ItxListViewContainer itxListViewContainer = listViewParser.ParseListView(mapParsingResult.FormDefMap.ListViewCont, serverMessage.Response);
                if (itxListViewContainer != null)
                {
                    // refresh formdef
                    mapParsingResult.FormDefMap.ListViewCont = itxListViewContainer;
                    if (itxListViewContainer.ListViews.Count > 0)
                        mapParsingResult.FormDefMap.ListViewCont.Display = true;
                }
                else
                    mapParsingResult.FormDefMap.ListViewCont.Display = false;
            }

            // parse bs field content
            var formDefMap = mapParsingResult.FormDefMap;
            mapParsingResult.BsMap.Status = bsParser.FillFormDefFields(mapParsingResult.BsMap.Header.CursorPos, sbCurrentBs, ref formDefMap);
            mapParsingResult.FormDefMap = formDefMap;

            // parse bs message area
            bsParser.ParseBsMessageArea(sbCurrentBs, mapParsingResult.BsMap);

            // check for listview
            if (!mapParsingResult.BsMap.Header.ListView && mapParsingResult.FormDefMap.ListViewCont != null)
                mapParsingResult.FormDefMap.ListViewCont.Display = false;

            //
            // graphics area
            if (mapParsingResult.BsMap.Header.Graphic)
            {
                try
                {
                    serverMessage = PerformOperation(ItxComOperation.ClientServer, ItxServerMessageType.Graphics, "");
                }
                catch (EncodingException exc)
                {
                    throw new CommunicationException(exc, exc.Message);
                }

                mapParsingResult.MapGraphics = GraphicsParser.SplitGraphicArea(serverMessage.Response);
            }
            mapParsingResult.CurrentBs = currentBs;
            return mapParsingResult;
        }

        /// <summary> request and parse the version and license information </summary>
        /// <param name="mapParsingResult"></param>
        private void RequestVersionAndLicense(MapParsingResult mapParsingResult)
        {
            ItxServerMessage serverMessage;
            // license
            try
            {
                serverMessage = PerformOperation(ItxComOperation.ClientServer, ItxServerMessageType.LicenseData, "");
            }
            catch (EncodingException exc)
            {
                throw new CommunicationException(exc, exc.Message);
            }
            mapParsingResult.LicenseInformation = AddonsParser.ParseLicenseInfo(serverMessage.Response);

            // version
            try
            {
                serverMessage = PerformOperation(ItxComOperation.ClientServer, ItxServerMessageType.VersionData, "");
            }
            catch (EncodingException exc)
            {
                throw new CommunicationException(exc, exc.Message);
            }
            mapParsingResult.VersionInformation = AddonsParser.ParseVersionInfo(serverMessage.Response);
        }

        /// <summary> request and parse the program explorer </summary>
        /// <param name="mapParsingResult"></param>
        private void RequestAndParseProgramExplorer(MapParsingResult mapParsingResult)
        {
            List<ItxProgramExplorerEntry> entries = new List<ItxProgramExplorerEntry>();
            try
            {
                // get first part
                var serverMessage = PerformOperation(ItxComOperation.ClientServer, ItxServerMessageType.ProgramExplorer, "");
                if (!string.IsNullOrEmpty(serverMessage.Response))
                {
                    // request until we received everything
                    while (true)
                    {
                        // parse received entries and add them to the list
                        var requestAddition = AddonsParser.ParseProgramExplorerEntry(serverMessage.Response, entries);
                        if (string.IsNullOrEmpty(requestAddition))
                            break;

                        // at the end?
                        if (serverMessage.Response.Length < 9000)
                            break;

                        // remove the last added, because we will receive it again
                        entries.RemoveAt(entries.Count - 1);

                        serverMessage = PerformOperation(ItxComOperation.ClientServer, ItxServerMessageType.ProgramExplorer, requestAddition);
                        if (string.IsNullOrEmpty(serverMessage.Response))
                            break;
                    }
                }
            }
            catch (EncodingException exc)
            {
                throw new CommunicationException(exc, exc.Message);
            }

            // now reorder program explorer for eliminiating any entry which is not inside the tree of gm01
            AddonsParser.ReOrderProgramExplorer(entries);

            // got any valid entries?
            if (entries.Count > 0)
                mapParsingResult.ProgramExplorer = entries;
        }

        private void RequestAndParseFavorites(MapParsingResult mapParsingResult)
        {
            List<ItxFavorite> favorites = new List<ItxFavorite>();
            try
            {
                // get first part
                var serverMessage = PerformOperation(ItxComOperation.ClientServer, ItxServerMessageType.Favorites, "");
                if (!string.IsNullOrEmpty(serverMessage.Response) && serverMessage.Response.Length > 122)
                {
                    // request until we received everything
                    while (true)
                    {
                        // parse received entries and add them to the list
                        string requestAddition = AddonsParser.ParseFavorite(serverMessage.Response, favorites);
                        if (string.IsNullOrEmpty(requestAddition))
                            break;

                        // request more!
                        serverMessage = PerformOperation(ItxComOperation.ClientServer, ItxServerMessageType.Favorites, requestAddition);
                        if (string.IsNullOrEmpty(serverMessage.Response) || serverMessage.Response.Length < 122)
                            break;
                    }
                }
            }
            catch (EncodingException exc)
            {
                throw new CommunicationException(exc, exc.Message);
            }

            // got any valid entries?
            if (favorites.Count > 0)
                mapParsingResult.Favorite = favorites;
        }

        /// <summary> handle chart data </summary>
        /// <param name="fileTransferResult"></param>
        /// <param name="fileAccessCheck"></param>
        /// <returns></returns>
        private ItxFileTransferResult HandleChartData(ItxFileTransferResult fileTransferResult, bool fileAccessCheck)
        {
            // at first, handle the file data - if filebinaryresponse is empty
            string fileContent = null;
            if (fileAccessCheck)
            {
                // try to read everything from the file
                try
                {
                    fileContent = File.ReadAllText(fileTransferResult.FilePath, Encoding.GetEncoding(850));
                }
                catch (IOException) {}
                catch (SecurityException) {}
                catch (UnauthorizedAccessException) {}
                catch (NotSupportedException) {}
            }
            else
            {
                fileContent = _codepageConverter.GetString(fileTransferResult.FileBinaryResponse, 0, fileTransferResult.FileBinaryResponse.Length);
                fileTransferResult.FileBinaryResponse = null;
            }

            // no data received, but just ignore and continue...
            if (string.IsNullOrEmpty(fileContent))
                fileContent = "";

            // parse the previously received chart data
            ChartParser chartParser = new ChartParser();
            ItxChartData chartData = chartParser.ParseChartData(fileContent);
            fileTransferResult.ChartData = chartData;
            return fileTransferResult;
        }

        /// <summary> Read the response from the stream </summary>
        /// <param name="inclSrvHeader">include the server header inside the return true/false</param>
        /// <returns></returns>
        /// <exception cref="CommunicationException"></exception>
        private ItxServerMessage GetDefaultResponse(bool inclSrvHeader)
        {
            // have we got some data available?
            try
            {
                while (_netStream != null && !_netStream.DataAvailable)
                {
                    // if we should stop the receive...
                    if (!_continueReceive)
                        return new ItxServerMessage(ItxServerMessageType.Stop);
                    Thread.Sleep(50);
                }
            }
            catch (ObjectDisposedException) {}

            // get...
            byte[] data = GetData();

            // convert at first the server header (70 bytes) to analyze the type
            StringBuilder sbResponse = new StringBuilder(_codepageConverter.GetString(data, 0, 70));
            // check if we received stop
            if (sbResponse.Contains("STOP"))
                return new ItxServerMessage(ItxServerMessageType.Stop);

            // parse server message
            ItxServerMessage serverMessage = new ItxServerMessage();
            ComHelper.ParseReceivedServerMessageType(sbResponse, serverMessage);

            // external data? - for message type EXT we mostly need to request more
            if (serverMessage.MessageType == ItxServerMessageType.ExternalData)
            {
                // convert the message without server header
                string response = _codepageConverter.GetString(data, 70, data.Length - 70);
                serverMessage.FileTransferResult = HandleExternalDataRequest(response);

                // so - now if we send ok we will receive the bs...
                SendMessage(ItxServerMessageType.Ok, "");

                // we will now receive the bs sentence - directly parse without server header
                data = GetData();
                sbResponse = new StringBuilder(_codepageConverter.GetString(data, 0, 70));

                // check if we received stop
                if (sbResponse.Contains("STOP"))
                    return new ItxServerMessage(ItxServerMessageType.Stop);
                ComHelper.ParseReceivedServerMessageType(sbResponse, serverMessage);
            }

            //
            // default communication - like bs/frm and so on
            if (serverMessage.MessageType != ItxServerMessageType.FileGet
                && serverMessage.MessageType != ItxServerMessageType.FileGetBinary)
            {
                // uncompress 
                if (serverMessage.MessageType == ItxServerMessageType.ScreenContentCompressed)
                    data = CompressionHelper.UnCompressMessage(data, serverMessage, ref _recvBufferBase);
                else if (serverMessage.MessageType == ItxServerMessageType.ScreenContent
                         && Connection.Compression == ItxCompression.ClientV5)
                {
                    /* we only reach here for the first message of the server
                     * - the compression is initiated from client, thus the server does not yet know about the compression
                     *   when sending the first message
                     * but for compression, we need to save the first received buffer inside the receive buffer for later comparison */
                    Array.Copy(data, 70, _recvBufferBase, 0, _recvBufferBase.Length);
                    if (data.Length - 70 < 9439)
                    {
                        for (int i = data.Length - 70; i < 9439; i++)
                            _recvBufferBase[i] = 0x20;
                    }
                }

                // this is default for bs/graph and so on...
                sbResponse.Append(_codepageConverter.GetString(data, 70, data.Length - 70));

                // remove server header?
                if (!inclSrvHeader)
                    sbResponse.Remove(0, 70);
                serverMessage.Response = sbResponse.ToString();

                return serverMessage;
            }

            // normal fileget should only be 'short' and no length for this stupid sentences between...
            if (serverMessage.MessageType != ItxServerMessageType.FileGet)
                return GetBinaryFileTransfer(data, serverMessage);

            serverMessage.FileBinaryResponse = new byte[data.Length - 70];
            Array.Copy(data, 70, serverMessage.FileBinaryResponse, 0, data.Length - 70);
            return serverMessage;
        }

        
        /// <summary> handle external data request - checking for the file on disk 
        /// and if not requesting / handle the requested </summary>
        /// <param name="response"></param>
        /// <returns></returns>
        private ItxFileTransferResult HandleExternalDataRequest(string response)
        {
            ItxFileTransferResult fileTransferResult = new ItxFileTransferResult();

            response = response.ToUpper();
            if (response.StartsWith("PAINTGRAF"))
            {
                fileTransferResult.FileDataType = ExternalServerDataType.Paintgraf;
                /* paintgraf will send us a file - sample data:
                    [PAINTGRAF G:\intex.ENT\intex\TEMP\T000239364]
                    we can also get:
                    [PAINTGRAF (LAST)G:\\intex.utf\\intex\\TEMP\\T000583988] */
                fileTransferResult.FilePath = response.Substring(10).Trim();
            }
            else if (response.StartsWith("ITEXCEL"))
            {
                fileTransferResult.FileDataType = ExternalServerDataType.ItExcel;
                string[] split = response.Split(' ');
                if (split.Length != 2)
                    throw new CommunicationException("Invalid filepath received!");
                fileTransferResult.FilePath = split[1].Trim();
            }
            else if (response.StartsWith("ITPRTVIEW.CMD"))
            {
                fileTransferResult.FileDataType = ExternalServerDataType.ItPrtView;
                //  [ITPRTVIEW.CMD D.002 G:\intex.ENT\intex\DRUCKE\D.002]
                // a little bit more complicated, we need to split with space and 3rd should
                //   be the path
                string[] split = response.Split(' ');
                if (split.Length != 3)
                    throw new CommunicationException("Invalid filepath received!");
                fileTransferResult.ProcedurName = split[0].Trim();
                fileTransferResult.FilePath = split[2].Trim();
            }
            else if (response.StartsWith("GETFILE"))
            {
                fileTransferResult.FileDataType = ExternalServerDataType.GetFile;
                string[] split = response.Split(' ');
                if (split.Length != 2)
                    throw new CommunicationException("Invalid filename received!");

                fileTransferResult.FilePath = split[1].Trim();
                fileTransferResult.OriginFileName = Path.GetFileName(split[1].Trim());
            }
            else if (response.StartsWith("ITMAIL"))
            {
                fileTransferResult.FileDataType = ExternalServerDataType.ItMail;
                string[] split = response.Split(' ');
                if (split.Length != 2)
                    throw new CommunicationException("Invalid filename received!");

                fileTransferResult.FilePath = split[1].Trim();
                fileTransferResult.OriginFileName = Path.GetFileName(split[1].Trim());
            }
            else
                fileTransferResult.FileRetrieveError = "Unknown data type received, please report support.";

            // got something of which we can take care?
            if (fileTransferResult.FileDataType == ExternalServerDataType.Unknown)
                return fileTransferResult;

            // check if we received 'last' in front of the file
            if (fileTransferResult.FilePath != null && fileTransferResult.FilePath.StartsWith("(LAST)"))
                fileTransferResult.FilePath = fileTransferResult.FilePath.Remove(0, 6);

            bool fileAccessCheck = false;
            if (File.Exists(fileTransferResult.FilePath))
            {
                // check read access
                try
                {
                    using (File.Open(fileTransferResult.FilePath, FileMode.Open, FileAccess.Read))
                    {
                        fileAccessCheck = true;
                    }
                }
                catch (IOException) {}
                catch (SecurityException) {}
                catch (UnauthorizedAccessException) {}
                catch (NotSupportedException) {}
            }

            /* if the file is not accessible we need to request it from server
                              [            000000034         FG           000110020                  G:\intex.ENT\intex\TEMP\T000239364]*/
            if (!fileAccessCheck)
            {
                // binary or normal fileget?
                switch (fileTransferResult.FileDataType)
                {
                    case ExternalServerDataType.Paintgraf:
                        SendMessage(ItxServerMessageType.FileGet, fileTransferResult.FilePath);
                        ItxServerMessage serverMessage = GetDefaultResponse(false);
                        if (serverMessage.FileTransferResult != null)
                            fileTransferResult.FileRetrieveError = serverMessage.FileTransferResult.FileRetrieveError;
                        fileTransferResult.FileBinaryResponse = serverMessage.FileBinaryResponse;
                        break;
                    case ExternalServerDataType.ItPrtView:
                    case ExternalServerDataType.GetFile:
                    case ExternalServerDataType.ItExcel:
                    case ExternalServerDataType.ItMail:
                        // recuresive request which will handle the file request and save to disk
                        SendMessage(ItxServerMessageType.FileGetBinary, fileTransferResult.FilePath);
                        ItxServerMessage serverMessage2 = GetDefaultResponse(false);
                        fileTransferResult.FileRetrieveError = serverMessage2.FileTransferResult.FileRetrieveError;
                        fileTransferResult.FilePath = serverMessage2.FileTransferResult.FilePath;
                        break;
                }
            }

            // additional action for chartdata
            if (fileTransferResult.FileDataType != ExternalServerDataType.Paintgraf)
                return fileTransferResult;

            if (string.IsNullOrEmpty(fileTransferResult.FileRetrieveError))
                fileTransferResult = HandleChartData(fileTransferResult, fileAccessCheck);
            else
                fileTransferResult.FileRetrieveError = fileTransferResult.FileRetrieveError;

            return fileTransferResult;
        }

        private ItxServerMessage GetBinaryFileTransfer(byte[] data, ItxServerMessage serverMessage)
        {
            // for binary fileget we assume that we will receive a very big file
            //   therefor we should write it directly into a tempfile...
            string finalPath = ComHelper.GetTempFile();
            _temporaryFiles.Add(finalPath);

            // now save the received byte array into the file 
            try
            {
                /* ok, we have a problem with parsing here
                            the reason is, that we get the data in 'sentences' which are not received packets
                            or anything else. In general we could just write out the received packages but then
                            we would also write out the length for the single sentences included in the form of Wnnnn (f.x. w2999
                            which would leave the file smashed...
                            therefor we need to remove them, but it may spread about several pakets... and we do not want to buffer
                            the whole file before writing the file
                            sadly, this will leave us only one chance to write a complicated method which we normally
                            try to avoid....
                            currently we try to think it as 'it should always be maximum and end up in next packet,
                            so we only will have a maximum of 2 lengths in one received batch (start and mid length)'..
                            if not, we're lost : )*/
                /* we do not want to write:
                                - the server header (beginning)     length 70
                                - the <msgno>W<length> (beginning)  length 4+1+4
                                - 9999/length to disk....           length 4 

                                and the length like 'W2999' mid inside the data */
                using (FileStream fs = new FileStream(finalPath, FileMode.Create, FileAccess.ReadWrite, FileShare.Read))
                {
                    bool firstRun = true;
                    bool packetsNeeded = true;
                    while (packetsNeeded)
                    {
                        /* as for it is the first message, we just receive
                                00002999 msgno(4),length(4) */
                        string firstBytes = _codepageConverter.GetString(data, 70, firstRun ? 8 : 9);

                        // check for an error from server
                        if (firstBytes.Substring(0, 4) != "0000")
                        {
                            serverMessage.FileTransferResult = new ItxFileTransferResult(firstBytes);
                            return serverMessage;
                        }

                        // we need to extract the length to remove the shit inside the file...
                        int length = ParseHelper.String2Int(firstBytes.Substring(firstRun ? 4 : 5, 4));
                        /* we remove:
                                        serverheader    70
                                        msg no          4 (already in firstbytes)
                                        length          4 (already in firstbytes) */
                        int dataArrayCopyOffset = firstRun ? 78 : 79;
                        if (data.Length - dataArrayCopyOffset > length)
                        {
                            /* write the 'sentence' defined by length until the next
                                        we copy the first part of current received batch, different header offset 
                                        (just Msgno/Length instead Msgno/W/Length) */
                            fs.Write(data, dataArrayCopyOffset, length);
                            dataArrayCopyOffset = dataArrayCopyOffset + length;

                            // offset behind next length - additionally parse length
                            //string length5Bytes = _codepageConverter.GetString(data, dataArrayCopyOffset, 5);
                            //length = ParseHelper.String2Int(length5Bytes.Substring(1, 4));
                            dataArrayCopyOffset = dataArrayCopyOffset + 5;

                            // write next part, but only if it is longer than 5 bytes (w0000 at end of paket)
                            int bytesToWrite = data.Length - dataArrayCopyOffset;
                            if (bytesToWrite > 5) // subtract length Wnnnn
                                bytesToWrite -= 5;

                            if (bytesToWrite > 0)
                                fs.Write(data, dataArrayCopyOffset, bytesToWrite);
                        }

                        string lastBytes = _codepageConverter.GetString(data, data.Length - 4, 4);
                        packetsNeeded = lastBytes != "9999";
                        if (packetsNeeded)
                        {
                            // get another binary response
                            SendMessage(ItxServerMessageType.FileGetBinary, "");
                            data = GetData();
                        }

                        firstRun = false;
                    }

                    // flush and close
                    fs.Flush(true);
                }
                serverMessage.FileTransferResult = new ItxFileTransferResult {FilePath = finalPath};
            }
            catch (IOException exc)
            {
                throw new CommunicationException(exc, "Unable to handle temp file: {0}", exc.Message);
            }
            catch (SecurityException exc)
            {
                throw new CommunicationException(exc, "Unable to handle temp file: {0}", exc.Message);
            }
            catch (UnauthorizedAccessException exc)
            {
                throw new CommunicationException(exc, "Unable to handle temp file: {0}", exc.Message);
            }
            catch (NotSupportedException exc)
            {
                throw new CommunicationException(exc, "Unable to handle temp file: {0}", exc.Message);
            }
            return serverMessage;
        }

        

        /// <summary> send passed string </summary>
        /// <param name="codePage"></param>
        /// <param name="message"></param>
        /// <param name="messageType"></param>
        /// <exception cref="CommunicationException"></exception>
        private void SendData(ItxCodepage codePage, string message, ItxServerMessageType messageType)
        {
            // convert to bytes
            byte[] data;
            try
            {
                data = _codepageConverter.GetBytes(message);
            }
            catch (EncodingException exc)
            {
                throw new CommunicationException(exc, exc.Message);
            }

            // bs message and compression? check 
            if (messageType == ItxServerMessageType.ScreenContent && Connection.Compression == ItxCompression.ClientV5)
                data = CompressionHelper.CompressMessage(data, ref _sendBufferBase);

            // finalize paket, adding length
            data = ComHelper.BuildPaket(codePage, Connection.Compression, data);

            //short paketLength = 1536;
            short paketLength = 8192;
            try
            {
                if (data.Length > paketLength)
                {
                    int totalSent = 0, sentData = paketLength;
                    while (totalSent < data.Length)
                    {
                        if (data.Length - totalSent < paketLength)
                            sentData = data.Length - totalSent;

                        _netStream.Write(data, totalSent, sentData);
                        totalSent = totalSent + paketLength;
                    }
                }
                else
                    _netStream.Write(data, 0, data.Length);
                _netStream.Flush();
            }
            catch (NullReferenceException exc)
            {
                throw new CommunicationException(exc, "Communication error: {0}", exc.Message);
            }
            catch (IOException exc)
            {
                throw new CommunicationException(exc, "Communication error: {0}", exc.Message);
            }
            catch (ObjectDisposedException exc)
            {
                throw new CommunicationException(exc, "Communication error: {0}", exc.Message);
            }
        }

        private byte[] GetData()
        {
            if (!_continueReceive)
                return null;

            if (_netStream == null)
                throw new CommunicationException("Transport layer closed.");
            /*
             * at first we get the length of the data which the server sent to us
             */
            byte[] recvLength = new byte[2];

            // read bytes from stream
            try
            {
                _netStream.Read(recvLength, 0, 2);
            }
            catch (IOException exc)
            {
                throw new CommunicationException(exc, "Error reading data: {0}", exc.Message);
            }

            // switch from lowHIGH to HIGHlow
            recvLength = new[] {recvLength[1], recvLength[0]};
            short length = BitConverter.ToInt16(recvLength, 0);

            // init return byte array
            byte[] data = new byte[length];

            /* 
             * now we read the data
             */
            //as4 got a different chunk size
            byte[] buff = _codepageConverter.IsAs400 ? new byte[511] : new byte[512];
            int complBytesRead = 0;

            // Incoming message may be larger than the buffer size.
            do
            {
                // check if we need a smaller buffer cause of passed length
                if (complBytesRead + buff.Length > length)
                    buff = new byte[length - complBytesRead];

                int curBytesRead;
                try
                {
                    curBytesRead = _netStream.Read(buff, 0, buff.Length);
                }
                catch (IOException exc)
                {
                    throw new CommunicationException(exc, "Error reading data: {0}", exc.Message);
                }

                buff.CopyTo(data, complBytesRead);
                complBytesRead = complBytesRead + curBytesRead;
            } while (complBytesRead < length);

            return data;
        }
    }
}